from typing import Dict, Any, Optional
from langgraph.graph import StateGraph
from pydantic import BaseModel, Field
from intelli_port.data_layer.clients import neo4j_exec, mysql_execute_read
from intelli_port.commons.config import load_settings
from intelli_port.commons.observability import nav_path_latency_ms, nav_success_total, nav_fail_total
import time


class ServiceNav(BaseModel):
    type: str = Field(default="navigate")
    origin: str
    destination: str
    steps: list[str]
    distance_m: Optional[float] = None
    duration_min: Optional[float] = None
    nodes: Optional[list[str]] = None
    edges: Optional[list[Dict[str, Any]]] = None


def build_navigate_subgraph():
    g = StateGraph(dict)

    def nav_prepare(state: Dict[str, Any]) -> Dict[str, Any]:
        sess = state.get("session") or {}
        slots = state.get("slots") or {}
        origin = slots.get("origin") or (sess or {}).get("location") or "当前所在位置"
        destination = slots.get("destination") or slots.get("gate") or slots.get("counter") or "目的地"
        return {**state, "slots": {**slots, "origin": origin, "destination": destination}}

    def nav_path_find(state: Dict[str, Any]) -> Dict[str, Any]:
        slots = state.get("slots") or {}
        origin = slots.get("origin") or "当前所在位置"
        destination = slots.get("destination") or "目的地"
        steps: list[str] = []
        prefer_accessible = bool(slots.get("prefer_accessible") or False)
        t0 = time.time()
        total_dist = None
        total_dur_min = None
        try:
            s = load_settings()
            kv_wd = mysql_execute_read("SELECT value FROM kv_config WHERE `key`=%s", ("nav.weight.w_dist",), fetch="one").get("rows", [])
            kv_wt = mysql_execute_read("SELECT value FROM kv_config WHERE `key`=%s", ("nav.weight.w_time",), fetch="one").get("rows", [])
            kv_wc = mysql_execute_read("SELECT value FROM kv_config WHERE `key`=%s", ("nav.weight.w_crowd",), fetch="one").get("rows", [])
            kv_pen = mysql_execute_read("SELECT value FROM kv_config WHERE `key`=%s", ("nav.weight.penalty",), fetch="one").get("rows", [])
            w_dist = float(kv_wd[0][0]) if kv_wd and kv_wd[0] and kv_wd[0][0] else 1.0
            w_time = float(kv_wt[0][0]) if kv_wt and kv_wt[0] and kv_wt[0][0] else 0.5
            w_crowd = float(kv_wc[0][0]) if kv_wc and kv_wc[0] and kv_wc[0][0] else 0.3
            penalty = float(kv_pen[0][0]) if kv_pen and kv_pen[0] and kv_pen[0][0] else 1000000.0
            ids = neo4j_exec("MATCH (a:POI {name:$src}),(b:POI {name:$dst}) RETURN id(a) AS srcId, id(b) AS dstId", {"src": origin, "dst": destination}) or []
            src_id = None
            dst_id = None
            if ids:
                src_id = ids[0].get("srcId")
                dst_id = ids[0].get("dstId")
            names = []
            mode = "weighted"
            if src_id is not None and dst_id is not None:
                neo4j_exec(
                    "CALL gds.graph.project.cypher('nav_tmp', 'MATCH (n:POI) RETURN id(n) AS id', "
                    "'MATCH (a:POI)-[e:PATH]-(b:POI) RETURN id(a) AS source, id(b) AS target, "
                    "{weight: $w_dist*COALESCE(e.distance_m,0.0) + $w_time*COALESCE(e.time_s,0.0) + $w_crowd*COALESCE(e.crowd_factor,1.0) + CASE WHEN $prefer_acc AND COALESCE(e.accessible,false)=false THEN $penalty ELSE 0 END + CASE WHEN COALESCE(e.crowd_factor,1.0) >= $crowd_th THEN $crowd_pen ELSE 0 END} AS properties')",
                    {"w_dist": w_dist, "w_time": w_time, "w_crowd": w_crowd, "prefer_acc": prefer_accessible, "penalty": penalty, "crowd_th": float((mysql_execute_read("SELECT value FROM kv_config WHERE `key`=%s", ("nav.crowd.threshold",), fetch="one").get("rows", []) or [["1.5"]])[0][0]), "crowd_pen": float((mysql_execute_read("SELECT value FROM kv_config WHERE `key`=%s", ("nav.crowd.penalty",), fetch="one").get("rows", []) or [["10.0"]])[0][0])},
                )
                r = neo4j_exec(
                    "CALL gds.shortestPath.dijkstra.stream('nav_tmp', {sourceNode:$srcId, targetNode:$dstId, relationshipWeightProperty:'weight'}) YIELD nodeId RETURN gds.util.asNode(nodeId).name AS name",
                    {"srcId": src_id, "dstId": dst_id},
                ) or []
                names = [x.get("name") for x in r if x.get("name")]
                neo4j_exec("CALL gds.graph.drop('nav_tmp') YIELD graphName")
            if not names:
                # 回退到无权最短路径
                q = (
                    "MATCH (a:POI {name:$src}),(b:POI {name:$dst}) "
                    "CALL algo.shortestPath.stream(a,b) YIELD nodeId "
                    "WITH nodeId MATCH (n) WHERE id(n)=nodeId RETURN n.name AS name"
                )
                r = neo4j_exec(q, {"src": origin, "dst": destination}) or []
                names = [x.get("name") for x in r if x.get("name")]
                mode = "unweighted"
            if names:
                for nm in names:
                    steps.append(f"前往 {nm}")
                # 汇总路径距离与时长，并构建结构化边信息
                try:
                    dist_sum = 0.0
                    dur_sum_s = 0.0
                    edges: list[Dict[str, Any]] = []
                    for i in range(len(names) - 1):
                        a = names[i]
                        b = names[i + 1]
                        rr = neo4j_exec(
                            "MATCH (a:POI {name:$a})-[e:PATH]->(b:POI {name:$b}) RETURN COALESCE(e.distance_m,0.0) AS dm, COALESCE(e.time_s,0.0) AS ts, COALESCE(e.accessible,false) AS acc, COALESCE(e.crowd_factor,1.0) AS crowd",
                            {"a": a, "b": b},
                        ) or []
                        dm = float((rr[0] or {}).get("dm") or 0.0) if rr and rr[0] is not None else 0.0
                        ts = float((rr[0] or {}).get("ts") or 0.0) if rr and rr[0] is not None else 0.0
                        acc = bool((rr[0] or {}).get("acc") or False) if rr and rr[0] is not None else False
                        crowd = float((rr[0] or {}).get("crowd") or 1.0) if rr and rr[0] is not None else 1.0
                        dist_sum += dm
                        dur_sum_s += ts
                        edges.append({"from": a, "to": b, "distance_m": dm, "time_s": ts, "accessible": acc, "crowd_factor": crowd})
                    total_dist = dist_sum if dist_sum > 0 else None
                    total_dur_min = (dur_sum_s / 60.0) if dur_sum_s > 0 else None
                except Exception:
                    edges = []
                    total_dist = None
                    total_dur_min = None
                svc = ServiceNav(origin=origin, destination=destination, steps=steps, distance_m=total_dist, duration_min=total_dur_min, nodes=names, edges=edges).model_dump()
                return {**state, "service": svc}
                nav_success_total.labels(mode=mode).inc()
            else:
                nav_fail_total.labels(mode="none", reason="no_path").inc()
        except Exception:
            steps = [f"从 {origin} 前往 {destination}"]
            nav_fail_total.labels(mode="error", reason="exception").inc()
        finally:
            try:
                nav_path_latency_ms.labels(mode="weighted").observe(max(0, (time.time() - t0) * 1000))
            except Exception:
                pass
        svc = ServiceNav(origin=origin, destination=destination, steps=steps, distance_m=total_dist, duration_min=total_dur_min).model_dump()
        return {**state, "service": svc}

    g.add_node("nav_prepare", nav_prepare)
    g.add_node("nav_path_find", nav_path_find)
    g.set_entry_point("nav_prepare")
    g.add_edge("nav_prepare", "nav_path_find")
    return g.compile()